#include <linux/mii.h>
#include <linux/phy.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include "fh_gmac_phyt.h"
#include <mach/pmu.h>
#include <mach/pinctrl.h>
#include <linux/platform_device.h>
#include <mach/fh_gmac_plat.h>

static int fh_mdio_set_mii(struct mii_bus *bus);

#ifndef PHY_MAX_ADDR
#define PHY_MAX_ADDR	32
#endif

unsigned int phy_support_list[] = {
	FH_GMAC_PHY_IP101G,
	FH_GMAC_PHY_RTL8201,
	FH_GMAC_PHY_TI83848,
	FH_GMAC_PHY_INTERNAL,
	FH_GMAC_PHY_DUMMY,
	FH_GMAC_PHY_INTERNAL_V2,
	FH_GMAC_PHY_INTERNAL_V3,
};

static int __fh_mdio_read(Gmac_Object *pGmac, int phyaddr, int phyreg)
{
	int ret;
#ifdef CONFIG_EMULATION
	return 0xe3ff;
#else
	int timeout = 1000;

	if (phyaddr < 0)
		return -ENODEV;


	writel(phyaddr << 11 | gmac_gmii_clock_100_150 << 2 | phyreg << 6 | 0x1,
	       pGmac->remap_addr + REG_GMAC_GMII_ADDRESS);

	while (readl(pGmac->remap_addr + REG_GMAC_GMII_ADDRESS) & 0x1) {
		udelay(100);
		timeout--;
		if (timeout < 0) {
			pr_err("ERROR: %s, timeout, phyaddr: 0x%x, phyreg: 0x%x\n",
					__func__, phyaddr, phyreg);
			break;
		}
	}
	ret = readl(pGmac->remap_addr + REG_GMAC_GMII_DATA);
	/*pr_err("add : reg : data = %x : %x : %x\n",phyaddr, phyreg, ret);*/
	return ret;
#endif

}

static int get_phy_id(Gmac_Object *gmac, int addr, u32 *phy_id)
{
	__u16 phy_reg;

	phy_reg = __fh_mdio_read(gmac, addr, MII_PHYSID1);
	*phy_id = (phy_reg & 0xffff) << 16;
	phy_reg = __fh_mdio_read(gmac, addr, MII_PHYSID2);
	*phy_id |= (phy_reg & 0xffff);

	return 0;
}


int check_white_list(__u32 phy_id, __u32 *list, __u32 size)
{
	int i;
	int ret = -1;

	for (i = 0; i < size; i++) {
		if (phy_id == list[i]) {
			ret = 0;
			break;
		}
	}
	return ret;
}

/* 0 find vaild, others failed*/
int scan_vaild_phy_id(Gmac_Object *gmac, __u32 *white_list, __u32 list_size)
{
	__u32 phy_id = 0;
	int i;
	int ret;

	for (i = 0; i < PHY_MAX_ADDR; i++) {
		ret = get_phy_id(gmac, i, &phy_id);
		if (ret == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) {
			if (check_white_list(phy_id, white_list, list_size))
				continue;
			break;
		}
	}
	if (i == PHY_MAX_ADDR)
		return -1;

	return 0;
}


int fh_phy_reset_api(struct phy_device *phydev)
{
	struct net_device *ndev;
	Gmac_Object *pGmac;

	pr_err("reset api get in....\n");
	ndev = phydev->mdio.bus->priv;
	pGmac = netdev_priv(ndev);

	if (pGmac->phy_sel)
		pGmac->phy_sel(pGmac->ac_phy_info->phy_sel);
	if (pGmac->inf_set)
		pGmac->inf_set(pGmac->ac_phy_info->phy_inf);
	if (pGmac->ac_phy_info->phy_reset)
		pGmac->ac_phy_info->phy_reset();

	fh_mdio_set_mii(pGmac->mii);
	return 0;
}
EXPORT_SYMBOL(fh_phy_reset_api);


int fh_phy_triger_api(struct phy_device *phydev)
{
	struct net_device *ndev;
	int ret;
	Gmac_Object *pGmac;

	ndev = phydev->mdio.bus->priv;
	pGmac = netdev_priv(ndev);
	if (pGmac->ac_phy_info->phy_triger) {
		ret = pGmac->ac_phy_info->phy_triger();
		if (ret != PHY_TRIGER_OK) {
			pr_err("got triger evet [%x]\n", ret);
			if (pGmac->phy_sel)
				pGmac->phy_sel(pGmac->ac_phy_info->phy_sel);
			if (pGmac->inf_set)
				pGmac->inf_set(pGmac->ac_phy_info->phy_inf);
			if (pGmac->ac_phy_info->phy_reset)
				pGmac->ac_phy_info->phy_reset();

			fh_mdio_set_mii(pGmac->mii);
		}
	}
	return 0;
}
EXPORT_SYMBOL(fh_phy_triger_api);

int auto_find_phy(struct net_device *ndev)
{
	int i;
	int ret = -1;
	struct fh_gmac_platform_data *p_info;
	struct platform_device *pdev;
	struct phy_interface_info *p_phy_info;

	Gmac_Object *pGmac = netdev_priv(ndev);

	pdev = pGmac->pdev;
	p_info  = (struct fh_gmac_platform_data *)pdev->dev.platform_data;

	if (!p_info) {
		pr_err("NULL plat info ....\n");
		return -1;
	}
	for (i = 0, p_phy_info = p_info->phy_info;
	i < ARRAY_SIZE(p_info->phy_info); i++) {
		//only parse usr set info.if not set ,just continue.
		if (!!p_phy_info[i].phy_sel) {
			//2:phy sel
			if (p_info->phy_sel)
				p_info->phy_sel(p_phy_info[i].phy_sel);
			//3:phy reset
			if (p_phy_info[i].phy_reset)
				p_phy_info[i].phy_reset();
			//4:mac interface set mii or rmii
			if (p_info->inf_set)
				p_info->inf_set(p_phy_info[i].phy_inf);
			/* find phy id.. */
			ret = scan_vaild_phy_id(pGmac, phy_support_list,
			ARRAY_SIZE(phy_support_list));
			if (!ret)
				break;
		}
	}
	if (!ret) {
		//find vaild phy...just parse
		pGmac->ac_phy_info = &p_phy_info[i];
		pGmac->phy_sel = p_info->phy_sel;
		pGmac->inf_set = p_info->inf_set;
		pGmac->phy_interface = p_phy_info[i].phy_inf;
		pr_err("auto find phy info :: %s : %s\n",
		p_phy_info[i].phy_sel == EXTERNAL_PHY ?
		"external phy" : "internal phy",
		p_phy_info[i].phy_inf == PHY_INTERFACE_MODE_MII ?
		"mii" : "rmii");
		//sync with net core later...
		return 0;
	}
	return -1;

}

static int __fh_mdio_write(Gmac_Object *pGmac, int phyaddr, int phyreg,
			 u16 phydata)
{
#ifdef CONFIG_EMULATION
	return 0;
#else
	int timeout = 1000;

	if (phyaddr < 0)
		return -ENODEV;

	writel(phydata, pGmac->remap_addr + REG_GMAC_GMII_DATA);
	writel(0x1 << 1 | phyaddr << 11 | gmac_gmii_clock_100_150 << 2 | phyreg
	       << 6 | 0x1, pGmac->remap_addr + REG_GMAC_GMII_ADDRESS);

	while (readl(pGmac->remap_addr + REG_GMAC_GMII_ADDRESS) & 0x1) {
		udelay(100);
		timeout--;
		if (timeout < 0) {
			pr_err("ERROR: %s, timeout, phyaddr: %d, phyreg: 0x%x, phydata: 0x%x\n",
					__func__, phyaddr, phyreg, phydata);
			break;
		}
	}
	return 0;
#endif

}


static int fh_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
{
	struct net_device *ndev = bus->priv;
	Gmac_Object *pGmac = netdev_priv(ndev);

	return __fh_mdio_read(pGmac, phyaddr, phyreg);
}

static int fh_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg,
			 u16 phydata)
{
	struct net_device *ndev = bus->priv;
	Gmac_Object *pGmac = netdev_priv(ndev);
	__u32 phy_id = 0;

#ifdef CONFIG_INTERNAL_PHY_RESET
	if ((phyreg == 0) && (phydata & (1 << 15))) {
		get_phy_id(pGmac, 0, &phy_id);
		get_phy_id(pGmac, 0, &phy_id);

		if (phy_id == FH_GMAC_PHY_INTERNAL_V2) {
			pGmac->ac_phy_info->phy_reset();

			return 0;
		}
	}
#endif

	return __fh_mdio_write(pGmac, phyaddr, phyreg, phydata);
}

int fh_mdio_reset(struct mii_bus *bus)
{
	//do nothing here. auto find phy has done.
	return 0;
}

static int fh_mdio_set_mii(struct mii_bus *bus)
{
	__u32 ret;
	struct phy_reg_cfg *p_cfg;
	struct phy_reg_cfg_list *p_cfg_data;

	struct net_device *ndev = bus->priv;
	Gmac_Object *pGmac = netdev_priv(ndev);

	int phyid = pGmac->phyid;

	if (pGmac->phydev == NULL)
		return -ENODEV;

#ifdef CONFIG_EMULATION
		return 0;
#endif

	p_cfg = (struct phy_reg_cfg *)pGmac->priv_data->p_cfg_array;
	for (; p_cfg->id != 0; p_cfg++) {
		if (p_cfg->id == pGmac->phydev->phy_id) {
			/*pr_err("find phy id [%08x] reg para\n",p_cfg->id);*/
			for (p_cfg_data = p_cfg->list;
			p_cfg_data->r_w != 0; p_cfg_data++) {
				if ((p_cfg_data->r_w & ACTION_MASK) == W_PHY)
					fh_mdio_write(bus, phyid,
					p_cfg_data->reg_add,
					p_cfg_data->reg_val);
				else if ((p_cfg_data->r_w & ACTION_MASK)
				== M_PHY) {
					ret = fh_mdio_read(bus, phyid,
					p_cfg_data->reg_add);
					ret &= ~(p_cfg_data->reg_mask);
					ret |= (p_cfg_data->reg_val &
					p_cfg_data->reg_mask);
					fh_mdio_write(bus, phyid,
					p_cfg_data->reg_add, ret);
				}
			}
			/*find one match;return ok*/
			return 0;
		}
	}
	return -ENODEV;
}

int fh_mdio_register(struct net_device *ndev)
{
	int err = 0, found, addr;
	struct mii_bus *new_bus;
	Gmac_Object *pGmac = netdev_priv(ndev);
	struct mdio_device *mdiodev = NULL;

	new_bus = mdiobus_alloc();
	if (new_bus == NULL)
		return -ENOMEM;
	new_bus->name =
	    pGmac->phy_interface ==
	    PHY_INTERFACE_MODE_MII ? "gmac_mii" : "gmac_rmii";
	new_bus->read = &fh_mdio_read;
	new_bus->write = &fh_mdio_write;
	new_bus->reset = &fh_mdio_reset;
	snprintf(new_bus->id, MII_BUS_ID_SIZE, "%x", 0);
	new_bus->priv = ndev;
	new_bus->parent = pGmac->dev;
	err = mdiobus_register(new_bus);
	if (err != 0) {
		pr_err("%s: Cannot register as MDIO bus, error: %d\n",
		       new_bus->name, err);
		goto bus_register_fail;
	}

	pGmac->mii = new_bus;

	found = 0;
	for (addr = 0; addr < 32; addr++) {
		mdiodev = new_bus->mdio_map[addr];
		if (mdiodev) {
			if (pGmac->phyid == -1)
				pGmac->phyid = addr;

			found = 1;
			pGmac->phydev = to_phy_device(&mdiodev->dev);
			break;
		}
	}

	if (pGmac->mii == NULL || mdiodev == NULL) {
		pr_warn("%s: MII BUS or phydev is NULL\n", ndev->name);
		err = -ENXIO;
		goto bus_register_fail;
	}

	err = fh_mdio_set_mii(pGmac->mii);

	if (!found || err) {
		pr_warn("%s: No PHY found\n", ndev->name);
		err = -ENXIO;
		goto bus_register_fail;
	}

	pr_info("%s: PHY ID %08x at %d IRQ %d (%s)%s\n",
		ndev->name, pGmac->phydev->phy_id, addr,
		pGmac->phydev->irq, dev_name(&pGmac->phydev->mdio.dev),
		(addr ==
		 pGmac->phyid) ? " active" : "");

	return 0;

bus_register_fail:
	pGmac->phydev = NULL;
	mdiobus_unregister(new_bus);
	mdiobus_free(new_bus);
	return err;
}

int fh_mdio_unregister(struct net_device *ndev)
{
	Gmac_Object *pGmac = netdev_priv(ndev);

	mdiobus_unregister(pGmac->mii);
	mdiobus_free(pGmac->mii);
	pGmac->mii->priv = NULL;
	pGmac->phydev = NULL;
	return 0;
}
